package com.autonavi.yunda.yunji.common.utils.geo;

import com.autonavi.yunda.yunji.common.utils.JsonUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * @author cizhong.wcs
 * @date 2022/4/11 14:34
 */
public class PolylineBlock {

    /**
     * 地球半径（米）
     */
    private static final int EARTH_RADIUS_METERS = 6371000;
    // 赤道半径
    private static final double Ea = 6378137;
    // 极半径
    private static final double Eb = 6356725;


    public static void main(String[] args) {
        System.out.println(distance(116.437662, 39.977014, 116.438600, 39.977014));
        System.out.println(JsonUtils.toString(splitLine(new GeoLatLon(39.977014, 116.437662), new GeoLatLon(39.977014, 116.438600), 100)));
        System.out.println(splitLine(new GeoLatLon(39.977014, 116.437662), new GeoLatLon(39.977014, 116.438600), 100).size());
        System.out.println(JsonUtils.toString(splitLineToGeoIds(new GeoLatLon(39.977014, 116.437662), new GeoLatLon(39.977014, 116.438600), 100)));
    }


    public static List<String> splitLineToGeoIds(GeoLatLon start, GeoLatLon end, double margin) {
        double angle = angle(start.getLon(), start.getLat(), end.getLon(), end.getLat());
        double dx = margin * Math.sin(angle * Math.PI / 180.0);
        double dy = margin * Math.cos(angle * Math.PI / 180.0);
        double distance = distance(start.lon, start.lat, end.lon, end.lat);
        List<String> result = new ArrayList<>();
        double lat = start.lat;
        double lng = start.lon;
        result.add(GeoBlockMgr.xy2BlockId("" + lng, "" + lat, (int) margin));
        for (double s = margin; s < distance; s += margin) {
            double ec = Eb + (Ea - Eb) * (90.0 - lat) / 90.0;
            double ed = ec * Math.cos(lat * Math.PI / 180);
            lng = (dx / ed + lng * Math.PI / 180.0) * 180.0 / Math.PI;
            lat = (dy / ec + lat * Math.PI / 180.0) * 180.0 / Math.PI;
            result.add(GeoBlockMgr.xy2BlockId("" + lng, "" + lat, (int) margin));
        }
        return result;
    }


    public static List<GeoLatLon> splitLine(GeoLatLon start, GeoLatLon end, double margin) {
        double angle = angle(start.getLon(), start.getLat(), end.getLon(), end.getLat());
        double dx = margin * Math.sin(angle * Math.PI / 180.0);
        double dy = margin * Math.cos(angle * Math.PI / 180.0);
        double distance = distance(start.lon, start.lat, end.lon, end.lat);
        List<GeoLatLon> result = new ArrayList<>();
        result.add(start);
        double lat = start.lat;
        double lng = start.lon;
        for (double s = margin; s < distance; s += margin) {
            double ec = Eb + (Ea - Eb) * (90.0 - lat) / 90.0;
            double ed = ec * Math.cos(lat * Math.PI / 180);
            lng = (dx / ed + lng * Math.PI / 180.0) * 180.0 / Math.PI;
            lat = (dy / ec + lat * Math.PI / 180.0) * 180.0 / Math.PI;
            result.add(new GeoLatLon(lat, lng));
        }
        return result;
    }

    /**
     * 地球弧面直线距离
     */
    public static double distance(double lon1, double lat1, double lon2, double lat2) {
        double latDistance = Math.toRadians(lat2 - lat1);
        double lonDistance = Math.toRadians(lon2 - lon1);
        double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        // convert to meters
        return EARTH_RADIUS_METERS * c;
    }

    /**
     * 根据一个2点AB的经纬度坐标、斜长距离r，计算线段AB上到A的距离为r的经纬度。
     *
     * @param margin 斜长距离(m)
     */
    public static double[] getNearPoint(double lng1, double lat1, double lng2, double lat2, double margin) {
        double angle = angle(lng1, lat1, lng2, lat2);
        return getNearPoint(lng1, lat1, margin, angle);
    }

    /**
     * 计算点1和点2的指向性角度，正北是0，顺时针增加。可选值0~360
     */
    public static double angle(Double lng_1, Double lat_1, Double lng_2, Double lat_2) {
        double lng1 = Math.toRadians(lng_1);
        double lng2 = Math.toRadians(lng_2);
        double lat1 = Math.toRadians(lat_1);
        double lat2 = Math.toRadians(lat_2);
        double _y = Math.sin(lng2 - lng1) * Math.cos(lat2);
        double _x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1);
        return (Math.toDegrees(Math.atan2(_y, _x)) + 360) % 360;
    }

    /**
     * 根据一个点的经纬度坐标、边长距离、角度计算另一个点的经纬度。
     *
     * @param lng    经度
     * @param lat    纬度
     * @param margin 斜长距离(m)
     * @param angle  角度（-180~180或0~360等价），正北为0度
     */
    public static double[] getNearPoint(double lng, double lat, double margin, double angle) {
        double[] lngLat = new double[2];
        double dx = margin * Math.sin(angle * Math.PI / 180.0);
        double dy = margin * Math.cos(angle * Math.PI / 180.0);
        double ec = Eb + (Ea - Eb) * (90.0 - lat) / 90.0;
        double ed = ec * Math.cos(lat * Math.PI / 180);
        double newLng = (dx / ed + lng * Math.PI / 180.0) * 180.0 / Math.PI;
        double newLat = (dy / ec + lat * Math.PI / 180.0) * 180.0 / Math.PI;
        lngLat[0] = newLng;
        lngLat[1] = newLat;
        return lngLat;
    }

}
